package com.ubi.core.framework;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.ubi.core.framework.request.model.Param;
import com.ubi.core.framework.test.TestProfileConstants;
import com.ubi.core.json.JsonConverter;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

/**
 * 单元测试基类. <br>
 * 类详细说明.
 * <p/>
 * Copyright: Copyright (c) 2016年06月30日 17:27
 * <p/>
 * Company:
 * <p/>
 *
 * @author Kent
 * @version 1.0.0
 */
@ActiveProfiles(TestProfileConstants.TEST)
@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration
@WebAppConfiguration
//@EnableAspectJAutoProxy
public class DefaultTestConfig {

    public final Logger logger = LoggerFactory.getLogger(getClass());

    public static final ResultMatcher OK = MockMvcResultMatchers.status().isOk();

    public static final ResultMatcher CLIENT_ERROR = MockMvcResultMatchers.status().is4xxClientError();

    public static final ResultMatcher SERVER_ERROR = MockMvcResultMatchers.status().is5xxServerError();

    protected JsonConverter jsonConverter;

    protected MockMvc mockMvc;

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired(required = false)
    @Qualifier("webJacksonObjectMapper")
    private ObjectMapper objectMapper;

    @Before
    public void createMockMVC() {

        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(new GlobalFilter()).build();

        if (jsonConverter == null) {
            if (objectMapper != null) {
                jsonConverter = new JsonConverter(objectMapper);
            } else {
                jsonConverter = new JsonConverter();
            }
        }
    }

    @Test
    public void applicationContextShouldBeInitialized() {

        Assert.assertNotNull(applicationContext);
        for (String beanDefinitionNames : applicationContext.getBeanDefinitionNames()) {
            Assert.assertNotNull("Bean is null,beanDefinitionNames :".concat(beanDefinitionNames),
                webApplicationContext.getBean(beanDefinitionNames));
        }
    }

    protected <T> ResultActions request(HttpMethod httpMethod, String uri, T requestContent, HttpHeaders httpHeaders,
        ResultMatcher resultMatcher) throws Exception {
        org.springframework.util.Assert.notNull(httpMethod);
        org.springframework.util.Assert.hasText(uri);
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.request(httpMethod, uri);

        builder.contentType(MediaType.APPLICATION_JSON);
        if (requestContent != null) {
            if (requestContent instanceof String) {
                builder.content((String) requestContent);
            } else {
                builder.content(jsonConverter.toJson(requestContent));
            }
        }
        builder.accept(MediaType.APPLICATION_JSON);
        builder.header("_traceLog", true);
        if (httpHeaders != null) {
            builder.headers(httpHeaders);
        }
        ResultActions resultActions = mockMvc.perform(builder).andDo(print());
        if (resultMatcher != null) {
            resultActions.andExpect(resultMatcher);
        }
        return resultActions;
    }

    protected ResultActions get(String url) throws Exception {
        return this.request(HttpMethod.GET, url, null, null, null);
    }

    protected ResultActions get(String url, HttpHeaders httpHeaders) throws Exception {
        return this.request(HttpMethod.GET, url, null, httpHeaders, null);
    }

    protected ResultActions get(String url, ResultMatcher resultMatcher) throws Exception {
        return this.request(HttpMethod.GET, url, null, null, resultMatcher);
    }

    protected ResultActions get(String url, HttpHeaders httpHeaders, ResultMatcher resultMatcher) throws Exception {
        return this.get(url, null, httpHeaders, resultMatcher);
    }

    protected <T> ResultActions get(String url, T t) throws Exception {
        return this.get(url, t, null, null);
    }

    protected <T> ResultActions get(String url, T t, HttpHeaders httpHeaders) throws Exception {
        return this.get(url, t, httpHeaders, null);
    }

    protected <T> ResultActions get(String url, T t, ResultMatcher resultMatcher) throws Exception {
        return this.get(url, t, null, resultMatcher);
    }

    protected <T> ResultActions get(String url, T t, HttpHeaders httpHeaders, ResultMatcher resultMatcher)
        throws Exception {
        org.springframework.util.Assert.hasText(url);
        String completeUrl = this.getCompleteUrl(url, t);
        return this.request(HttpMethod.GET, completeUrl, null, httpHeaders, resultMatcher);
    }

    // TODO(chenwentao) 2016/2/29: 写成工具类
    private <T> String getCompleteUrl(String url, T t)
        throws IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        Class tClass = t.getClass();
        Method[] allMethods = tClass.getMethods();
        Map<String, Object> parameters = Maps.newLinkedHashMap();
        List<Field> allFields = getInheritedPrivateFields(t.getClass());

        for (Method method : allMethods) {
            if (!StringUtils.equals(method.getName(), "getClass") && method.getName().startsWith("get")) {
                String fieldName = lowerFirstCharacter(StringUtils.removeStart(method.getName(), "get"));
                Field field = lookupField(allFields, fieldName);
                if (field == null) {
                    System.out.println("cloud not find field by fieldName:" + fieldName);
                    continue;
                }
                Param param = field.getAnnotation(Param.class);
                String parameterName = param == null ? fieldName : param.value();
                Object parameterValue = method.invoke(t);
                if (parameterValue == null) {
                    continue;
                }
                parameters.put(parameterName, parameterValue);
            }
        }
        StringBuilder urlBuilder = new StringBuilder(url);
        boolean isAppendQuestionMark = !url.contains("?");
        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
            String parameterName = entry.getKey();
            Object parameterValue = entry.getValue();
            if (isAppendQuestionMark) {
                urlBuilder.append('?');
                isAppendQuestionMark = false;
            } else {
                urlBuilder.append('&');
            }
            urlBuilder.append(parameterName);
            urlBuilder.append('=');
            urlBuilder.append(parameterValue);
        }
        return urlBuilder.toString();
    }

    private Field lookupField(List<Field> fields, String fieldName) {
        for (Field field1 : fields) {
            if (field1.getName().equals(fieldName))
                return field1;
        }
        return null;
    }

    private List<Field> getInheritedPrivateFields(Class<?> type) {
        List<Field> result = new ArrayList<>();
        Class<?> i = type;
        while (i != null && i != Object.class) {
            for (Field field : i.getDeclaredFields()) {
                if (!field.isSynthetic()) {
                    result.add(field);
                }
            }
            i = i.getSuperclass();
        }
        return result;
    }

    protected ResultActions post(String url) throws Exception {
        return this.request(HttpMethod.POST, url, null, null, null);
    }

    protected <T> ResultActions post(String url, T content) throws Exception {
        return this.request(HttpMethod.POST, url, content, null, null);
    }

    protected <T> ResultActions post(String url, T content, HttpHeaders httpHeaders) throws Exception {
        return this.request(HttpMethod.POST, url, content, httpHeaders, null);
    }

    protected <T> ResultActions post(String url, T content, ResultMatcher resultMatcher) throws Exception {
        return this.request(HttpMethod.POST, url, content, null, resultMatcher);
    }

    protected ResultActions put(String url) throws Exception {
        return this.request(HttpMethod.PUT, url, null, null, null);
    }

    protected <T> ResultActions put(String url, T content) throws Exception {
        return this.request(HttpMethod.PUT, url, content, null, null);
    }

    protected <T> ResultActions put(String url, T content, HttpHeaders httpHeaders) throws Exception {
        return this.request(HttpMethod.PUT, url, content, httpHeaders, null);
    }

    protected <T> ResultActions put(String url, T content, ResultMatcher resultMatcher) throws Exception {
        return this.request(HttpMethod.PUT, url, content, null, resultMatcher);
    }

    protected ResultActions delete(String url) throws Exception {
        return this.request(HttpMethod.DELETE, url, null, null, null);
    }

    protected <T> ResultActions delete(String url, T content) throws Exception {
        return this.request(HttpMethod.DELETE, url, content, null, null);
    }

    protected <T> ResultActions delete(String url, T content, HttpHeaders httpHeaders) throws Exception {
        return this.request(HttpMethod.DELETE, url, content, httpHeaders, null);
    }

    protected <T> ResultActions delete(String url, T content, ResultMatcher resultMatcher) throws Exception {
        return this.request(HttpMethod.DELETE, url, content, null, resultMatcher);
    }

    protected ResultActions fileUpload(String url, String fileName, byte[] fileBytes, ResultMatcher resultMatcher)
        throws Exception {
        MockMultipartFile mockMultipartFile = new MockMultipartFile("file", fileName, null, fileBytes);
        return mockMvc.perform(MockMvcRequestBuilders.fileUpload(url).file(mockMultipartFile)).andDo(print())
            .andExpect(resultMatcher);
    }

    protected static String lowerFirstCharacter(String str) {
        if (StringUtils.isEmpty(str))
            return str;
        char[] arrays = str.toCharArray();
        char firstUpperCaseChar = arrays[0];
        char firstLowercaseChar = Character.toLowerCase(firstUpperCaseChar);
        arrays[0] = firstLowercaseChar;
        return new String(arrays);
    }
}
